<?php
    // +----------------------------------------------------------------------
    // | ThinkPHP [ WE CAN DO IT JUST THINK ]
    // +----------------------------------------------------------------------
    // | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
    // +----------------------------------------------------------------------
    // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
    // +----------------------------------------------------------------------
    // | Author: yunwuxin <448901948@qq.com>
    // +----------------------------------------------------------------------
    namespace think\console\input;
    class Definition {
        /**
         * @var Argument[]
         */
        private $arguments;
        private $requiredCount;
        private $hasAnArrayArgument = false;
        private $hasOptional;
        /**
         * @var Option[]
         */
        private $options;
        private $shortcuts;

        /**
         * 构造方法
         * @param array $definition
         * @api
         */
        public function __construct(array $definition = []) {
            $this->setDefinition($definition);
        }

        /**
         * 设置指令的定义
         * @param array $definition 定义的数组
         */
        public function setDefinition(array $definition) {
            $arguments = [];
            $options   = [];
            foreach ($definition as $item) {
                if ($item instanceof Option) {
                    $options[] = $item;
                } else {
                    $arguments[] = $item;
                }
            }
            $this->setArguments($arguments);
            $this->setOptions($options);
        }

        /**
         * 添加参数
         * @param Argument[] $arguments 参数数组
         * @api
         */
        public function addArguments($arguments = []) {
            if (null !== $arguments) {
                foreach ($arguments as $argument) {
                    $this->addArgument($argument);
                }
            }
        }

        /**
         * 添加一个参数
         * @param Argument $argument 参数
         * @throws \LogicException
         */
        public function addArgument(Argument $argument) {
            if (isset($this->arguments[$argument->getName()])) {
                throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
            }
            if ($this->hasAnArrayArgument) {
                throw new \LogicException('Cannot add an argument after an array argument.');
            }
            if ($argument->isRequired() && $this->hasOptional) {
                throw new \LogicException('Cannot add a required argument after an optional one.');
            }
            if ($argument->isArray()) {
                $this->hasAnArrayArgument = true;
            }
            if ($argument->isRequired()) {
                ++$this->requiredCount;
            } else {
                $this->hasOptional = true;
            }
            $this->arguments[$argument->getName()] = $argument;
        }

        /**
         * 根据名称或者位置获取参数
         * @param string|int $name 参数名或者位置
         * @return Argument 参数
         * @throws \InvalidArgumentException
         */
        public function getArgument($name) {
            if (!$this->hasArgument($name)) {
                throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
            }
            $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
            return $arguments[$name];
        }

        /**
         * 根据名称或位置检查是否具有某个参数
         * @param string|int $name 参数名或者位置
         * @return bool
         * @api
         */
        public function hasArgument($name) {
            $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
            return isset($arguments[$name]);
        }

        /**
         * 获取参数数量
         * @return int
         */
        public function getArgumentCount() {
            return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
        }

        /**
         * 获取必填的参数的数量
         * @return int
         */
        public function getArgumentRequiredCount() {
            return $this->requiredCount;
        }

        /**
         * 获取参数默认值
         * @return array
         */
        public function getArgumentDefaults() {
            $values = [];
            foreach ($this->arguments as $argument) {
                $values[$argument->getName()] = $argument->getDefault();
            }
            return $values;
        }

        /**
         * 添加选项
         * @param Option[] $options 选项数组
         * @api
         */
        public function addOptions($options = []) {
            foreach ($options as $option) {
                $this->addOption($option);
            }
        }

        /**
         * 添加一个选项
         * @param Option $option 选项
         * @throws \LogicException
         * @api
         */
        public function addOption(Option $option) {
            if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
                throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
            }
            if ($option->getShortcut()) {
                foreach (explode('|', $option->getShortcut()) as $shortcut) {
                    if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
                        throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
                    }
                }
            }
            $this->options[$option->getName()] = $option;
            if ($option->getShortcut()) {
                foreach (explode('|', $option->getShortcut()) as $shortcut) {
                    $this->shortcuts[$shortcut] = $option->getName();
                }
            }
        }

        /**
         * 根据名称检查某个选项是否有短名称
         * @param string $name 短名称
         * @return bool
         */
        public function hasShortcut($name) {
            return isset($this->shortcuts[$name]);
        }

        /**
         * 根据短名称获取选项
         * @param string $shortcut 短名称
         * @return Option
         */
        public function getOptionForShortcut($shortcut) {
            return $this->getOption($this->shortcutToName($shortcut));
        }

        /**
         * 根据名称获取选项
         * @param string $name 选项名
         * @return Option
         * @throws \InvalidArgumentException
         * @api
         */
        public function getOption($name) {
            if (!$this->hasOption($name)) {
                throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
            }
            return $this->options[$name];
        }

        /**
         * 根据名称检查是否有这个选项
         * @param string $name 选项名
         * @return bool
         * @api
         */
        public function hasOption($name) {
            return isset($this->options[$name]);
        }

        /**
         * 根据短名称获取选项名
         * @param string $shortcut 短名称
         * @return string
         * @throws \InvalidArgumentException
         */
        private function shortcutToName($shortcut) {
            if (!isset($this->shortcuts[$shortcut])) {
                throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
            }
            return $this->shortcuts[$shortcut];
        }

        /**
         * 获取所有选项的默认值
         * @return array
         */
        public function getOptionDefaults() {
            $values = [];
            foreach ($this->options as $option) {
                $values[$option->getName()] = $option->getDefault();
            }
            return $values;
        }

        /**
         * 获取该指令的介绍
         * @param bool $short 是否简洁介绍
         * @return string
         */
        public function getSynopsis($short = false) {
            $elements = [];
            if ($short && $this->getOptions()) {
                $elements[] = '[options]';
            } elseif (!$short) {
                foreach ($this->getOptions() as $option) {
                    $value = '';
                    if ($option->acceptValue()) {
                        $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '');
                    }
                    $shortcut   = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
                    $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
                }
            }
            if (count($elements) && $this->getArguments()) {
                $elements[] = '[--]';
            }
            foreach ($this->getArguments() as $argument) {
                $element = '<' . $argument->getName() . '>';
                if (!$argument->isRequired()) {
                    $element = '[' . $element . ']';
                } elseif ($argument->isArray()) {
                    $element .= ' (' . $element . ')';
                }
                if ($argument->isArray()) {
                    $element .= '...';
                }
                $elements[] = $element;
            }
            return implode(' ', $elements);
        }

        /**
         * 获取所有选项
         * @return Option[]
         * @api
         */
        public function getOptions() {
            return $this->options;
        }

        /**
         * 设置选项
         * @param Option[] $options 选项数组
         */
        public function setOptions($options = []) {
            $this->options   = [];
            $this->shortcuts = [];
            $this->addOptions($options);
        }

        /**
         * 获取所有的参数
         * @return Argument[] 参数数组
         */
        public function getArguments() {
            return $this->arguments;
        }

        /**
         * 设置参数
         * @param Argument[] $arguments 参数数组
         */
        public function setArguments($arguments = []) {
            $this->arguments          = [];
            $this->requiredCount      = 0;
            $this->hasOptional        = false;
            $this->hasAnArrayArgument = false;
            $this->addArguments($arguments);
        }
    }
